package server;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.reflect.ConstructorUtils;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.nio.BlockingChannelConnector;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import annotation.Inject;
import annotation.Service;
import common.ClassHelper;
import common.ConfigHelper;

public class Applications
{
    String SERVICE_NAME = "server.Service";
    Map<String, Handler> handlerMap = new LinkedHashMap<String, Handler>();
    Map<String, Object> applicationContext = new HashMap<String, Object>();
    Server coreWorkJettryServer;
    Server maintenanceJettyServer;
    ArrayList<ContextHandler> contextList;
    boolean isDebug = true;

    public Applications(Map<String, Object> args) throws Exception
    {
        this.applicationContext.putAll(args);
        prepare();
        init();
    }

    public void prepare() throws Exception
    {
        preparejettyServer();
        prepareHandler();
    }

    private void prepareHandler() throws Exception
    {

        Map<String, Handler> servicesHanlderMap = new HashMap<String, Handler>();
        contextList = new ArrayList<ContextHandler>();

        List<Class<?>> services = ClassHelper.getClassListByAnnotation(Service.class);
        if (isHttpServerSupport() || CollectionUtils.isEmpty(services))
        {
            setDefaultContext(servicesHanlderMap);
        }
        // init those context service base on path.
        for (Class clazz : services)
        {
            if (!Handler.class.isAssignableFrom(clazz))
            {
                continue;
            }
            Service serviceAnnotation = (Service) clazz.getAnnotation(Service.class);
            String name = serviceAnnotation.name();
            String path = serviceAnnotation.applaction();
            String[] otherPath = serviceAnnotation.subjoin();
            String[] contextArray = null;
            if (otherPath.length > 0)
            {
                contextArray = new String[1 + otherPath.length];
                contextArray[0] = path;
                System.arraycopy(otherPath, 0, contextArray, 1, otherPath.length);
            }
            else
            {
                contextArray = new String[] { path };
            }
            // should be support the constructor mode.
            Handler handler = (Handler) clazz.newInstance();
            servicesHanlderMap.put(name, handler);

            for(String schame:contextArray)
            {
                if(StringUtils.isNotBlank(schame))
                {
                    ContextHandler context = new ContextHandler();
                    context.setContextPath(schame);
                    context.setHandler(handler);
                    contextList.add(context);
                }
            }
        }
        // inject those service need parameters.
        for (Map.Entry<String, Handler> entry : servicesHanlderMap.entrySet())
        {
            Handler handler = entry.getValue();
            Class handlerClass = handler.getClass();
            // first inject class fields.
            Field[] fields = handlerClass.getDeclaredFields();
            if (ArrayUtils.isEmpty(fields))
            {
                continue;
            }
            injectFieldProvider(fields, handler);

            Method[] methods = handlerClass.getMethods();
            if (ArrayUtils.isEmpty(methods))
            {
                continue;
            }
            injectMethodProvider(methods, handler);

        }
        // add exclude path into Resourcehandler

        //
        handlerMap.clear();
        handlerMap.putAll(servicesHanlderMap);

    }

    private void preparejettyServer() throws Exception
    {
        JettryConfiguration config = new JettryConfiguration(applicationContext);

        ArrayList<Connector> connectors = new ArrayList<Connector>();
        if (config.privatePort > 0)
        {
            connectors.add(createConnector(config.connectorType, config.privatePort, config.acceptQueueSize));
        }
        if (config.publicPort > 0)
        {
            connectors.add(createConnector(config.connectorType, config.publicPort, config.acceptQueueSize));
        }
        if (config.sslPort > 0)
        {
            SslSelectChannelConnector connector = new SslSelectChannelConnector();
            connector.setPort(config.sslPort);
            connector.setAcceptQueueSize(config.acceptQueueSize);
            connector.setKeyPassword(config.keyPassword);
            if (config.keystoreFile != null)
            {
                connector.setKeystore(config.keystoreFile);
            }
            connectors.add(connector);
        }
        for (Connector connector : connectors)
        {
            connector.setMaxIdleTime(config.keepAlive);
            connector.setResponseBufferSize(config.sendBufferSize);
        }
        if (connectors.size() > 0)
        {
            Server jetty = new Server();
            Connector[] array = new Connector[connectors.size()];
            connectors.toArray(array);
            jetty.setConnectors(array);

            if (config.minThreads < config.maxThreads)
            {
                ThreadPool pool = new ExecutorThreadPool(config.minThreads, config.maxThreads, config.keepAlive);
                jetty.setThreadPool(pool);
            }
            else
            {
                ThreadPool pool = new QueuedThreadPool(config.maxThreads);
                jetty.setThreadPool(pool);
            }
            setCoreWorkJettryServer(jetty);
        }

        if (config.maintenancePort > 0)
        {
            Server maintenanceJetty = new Server();
            maintenanceJetty.setConnectors(new Connector[] { createConnector(config.connectorType,
                config.maintenancePort,
                config.acceptQueueSize) });
            setMaintenanceJettyServer(maintenanceJetty);
        }
    }

    public static class JettryConfiguration
    {
        public int maintenancePort = 0;
        public int privatePort = 41801;
        public int publicPort = 41802;
        public int sslPort = 0;
        public String keystoreFile = null;
        public String keyPassword = "";
        public int acceptQueueSize = 5;
        public int keepAlive = 0;
        public int sendBufferSize = 1000000;
        public int maxThreads = 1000;
        public int minThreads = 1000;
        String connectorType = "SelectChannelConnector";

        JettryConfiguration(Map<String, Object> args)
        {
            init(args);
        }

        private void init(Map<String, Object> args)
        {
            maintenancePort = getIntValue(args, AppConstant.APPLICATION_MAINTENANCE_PORT, maintenancePort);
            privatePort = getIntValue(args, AppConstant.APPLICATION_PRIVATE_PORT, privatePort);
            publicPort = getIntValue(args, AppConstant.APPLICATION_PUBLIC_PORT, publicPort);
            sslPort = getIntValue(args, AppConstant.APPLICATION_SSL_PORT, sslPort);
            acceptQueueSize = getIntValue(args, AppConstant.APPLICATION_ACCEPT_QUEUE_SIZE, acceptQueueSize);
            keepAlive = getIntValue(args, AppConstant.APPLICATION_KEEP_ALIVE, keepAlive);
            sendBufferSize = getIntValue(args, AppConstant.APPLICATION_SEND_BUFFER_SIZE, sendBufferSize);
            maxThreads = getIntValue(args, AppConstant.APPLICATION_MAX_THREADS, maxThreads);
            minThreads = getIntValue(args, AppConstant.APPLICATION_MIN_THREADS, minThreads);
            keystoreFile = getStrValue(args, AppConstant.APPLICATION_SSL_KEYSTORE_FILE, keystoreFile);
            keyPassword = getStrValue(args, AppConstant.APPLICATION_SSL_KEY_PASSWORD, keyPassword);
            connectorType = getStrValue(args, AppConstant.APPLICATION_CONNECTOR_TYPE, connectorType);
        }

        private String getStrValue(Map<String, Object> args, String name, String defaultValue)
        {
            return args.containsKey(name) ? args.get(name).toString()
                            : defaultValue;
        }

        private int getIntValue(Map<String, Object> args, String name, int defautValue)
        {
            if (!args.containsKey(name))
            {
                return defautValue;
            }
            Object obj = args.get(name);
            if (obj instanceof Integer)
            {
                return (Integer) obj;
            }
            String value = obj.toString();

            if (!NumberUtils.isNumber(value))
            {
                return defautValue;
            }
            return NumberUtils.toInt(value);
        }
    }

    private AbstractConnector createConnector(String connectorType, int port, int acceptQueueSize) throws Exception
    {
        AbstractConnector connector = null;
        if (connectorType.equals(BlockingChannelConnector.class.getSimpleName()))
        {
            connector = new BlockingChannelConnector();
            connector.setPort(port);
            connector.setAcceptQueueSize(acceptQueueSize);
        }
        else if (connectorType.equals(SelectChannelConnector.class.getSimpleName()))
        {
            connector = new SelectChannelConnector();
            connector.setPort(port);
            connector.setAcceptQueueSize(acceptQueueSize);
        }

        else
        {
            throw new Exception("Unsupported connector type " + connectorType);
        }
        return connector;
    }

    public void init() throws Exception
    {
        ContextHandlerCollection contexts = new ContextHandlerCollection();
        int size = contextList.size();
        contexts.setHandlers(contextList.toArray(new Handler[size]));
        getCoreWorkJettryServer().setHandler(contexts);
    }

    private void injectMethodProvider(Method[] methods, Handler handler)
    {
        for (Method method : methods)
        {
            for (Annotation anno : method.getDeclaredAnnotations())
            {
                if (anno.annotationType().isAssignableFrom(Inject.class))
                {
                    Inject inject = (Inject) anno;
                    Class clazz = inject.type();
                    String param = inject.param();
                    String propertystr = ConfigHelper.getStringProperty(param);
                    try
                    {
                        Object obj = ConstructorUtils.invokeConstructor(clazz, propertystr);
                        method.invoke(handler, obj);
                    }
                    catch (Exception e1)
                    {
                        e1.printStackTrace();
                    }
                    //
                }
            }
        }
    }

    private void injectFieldProvider(Field[] fields, Handler handler)
    {

    }

    private void injectMethodProvider(Method[] methods)
    {

    }

    private void injectFieldProvider(Field[] fields)
    {
        for (Field beanField : fields)
        {
            if (beanField.isAnnotationPresent(Inject.class))
            {
                Inject fieldInject = beanField.getAnnotation(Inject.class);
                Class<?> interfaceClass = beanField.getType();
                String name = fieldInject.name();

            }
        }
    }

    private Handler createHandler(Map<String, Handler> servicesHanlderMap)
    {
        // TODO Auto-generated method stub
        return null;
    }

    private void setDefaultContext(Map<String, Handler> servicesHanlderMap) throws MalformedURLException, IOException
    {
        String defaultPath = "/";// TODO need load this parameter from properties file.

        ContextHandler defaultHandler = new ContextHandler();
        defaultHandler.setContextPath(defaultPath);

        ExcludablePathResourceHandler resource = new ExcludablePathResourceHandler();
        // TODO need load this parameter from properties file.
        String rootPath = ConfigHelper.getStringProperty("service.http.root", "src/main/resources");
        File pageRoot = new File(System.getProperty("user.dir"), rootPath);
        resource.setBaseResource(Resource.newResource(pageRoot));
        resource.setWelcomeFiles(new String[] { "index.html" });
        if (isDebug)
        {
            resource.setDirectoriesListed(true);
        }
        defaultHandler.setHandler(resource);

        servicesHanlderMap.put(defaultPath, resource);
        contextList.add(defaultHandler);
    }

    public void onReady(Server server)
    {

    }

    private void start(Server server) throws Exception
    {
        if (null != server)
        {
            server.start();
            // server.join();
        }
    }

    private void close(Server server) throws Exception
    {
        if (null != server)
        {
            server.stop();
        }
    }

    public void run() throws Exception
    {
        start(getCoreWorkJettryServer());
        start(getMaintenanceJettyServer());
    }

    public void shutdown() throws Exception
    {
        close(getCoreWorkJettryServer());
        close(getMaintenanceJettyServer());
    }

    public Server getCoreWorkJettryServer()
    {
        return coreWorkJettryServer;
    }

    public void setCoreWorkJettryServer(Server coreWorkJettryServer)
    {
        this.coreWorkJettryServer = coreWorkJettryServer;
    }

    public Server getMaintenanceJettyServer()
    {
        return maintenanceJettyServer;
    }

    public void setMaintenanceJettyServer(Server maintenanceJettyServer)
    {
        this.maintenanceJettyServer = maintenanceJettyServer;
    }

    public boolean isRunable()
    {
        return getCoreWorkJettryServer().isRunning();
    }

    private boolean isHttpServerSupport()
    {
        String str = ConfigHelper.getStringProperty("service.isHttp", "false");
        return BooleanUtils.toBoolean(str);
    }
}
